﻿Imports OpenTK
Imports OpenTK.Graphics
Imports OpenTK.Graphics.OpenGL4
Imports Assimp
Imports OpenTK.Input
Imports System
Imports System.Drawing.Drawing2D
Imports System.IO
Imports System.Runtime.InteropServices
Imports System.ComponentModel
Imports System.Drawing

REM ref: https://github.com/LWJGL/lwjgl3-wiki/wiki/2.6.1.-Ray-tracing-with-OpenGL-Compute-Shaders-(Part-I)
REM compute shader ref:http://wili.cc/blog/opengl-cs.html

Module test
    Dim testshader = "
#version 430
layout (local_size_x = 1,local_size_y = 1) in;
layout (binding = 0) uniform atomic_uint counter;
layout (packed, binding = 1) writeonly 
buffer block0
{
    uint index[];
};

void main()
{
    uint i = atomicCounterIncrement(counter);
    index[i] = i * i;
}"

    Public Sub TestACB()
        Dim acb = GL.GenBuffer()
        Dim ssbo = GL.GenBuffer()
        GL.BindBuffer(BufferTarget.AtomicCounterBuffer, acb)
        GL.BufferData(BufferTarget.AtomicCounterBuffer, 4, IntPtr.Zero, BufferUsageHint.DynamicCopy)

        GL.BindBuffer(BufferTarget.ShaderStorageBuffer, ssbo)
        GL.BufferData(BufferTarget.ShaderStorageBuffer, 4 * 64, IntPtr.Zero, BufferUsageHint.DynamicCopy)

        Dim program = GL.CreateProgram()
        Dim shader = GL.CreateShader(ShaderType.ComputeShader)
        GL.ShaderSource(shader, testshader)
        GL.CompileShader(shader)
        Dim shaderinfo = GL.GetShaderInfoLog(shader)
        Console.WriteLine(shaderinfo)
        GL.AttachShader(program, shader)
        GL.LinkProgram(program)
        Dim programinfo = GL.GetProgramInfoLog(program)
        Console.WriteLine(programinfo)
        GL.UseProgram(program)

        GL.BindBufferBase(BufferRangeTarget.AtomicCounterBuffer, 0, acb)
        GL.BindBufferBase(BufferRangeTarget.ShaderStorageBuffer, 1, ssbo)
        GL.DispatchCompute(64, 1, 1)

        Dim ptr = GL.MapBuffer(BufferTarget.AtomicCounterBuffer, BufferAccess.ReadOnly)
        GL.UnmapBuffer(BufferTarget.AtomicCounterBuffer)
        Dim ptr2 = GL.MapBuffer(BufferTarget.ShaderStorageBuffer, BufferAccess.ReadOnly)
        For i = 0 To 64 - 1
            Dim k = System.Runtime.InteropServices.Marshal.ReadInt32(ptr2, i * 4)
            Console.Write(k.ToString() + vbTab)
        Next
        GL.UnmapBuffer(BufferTarget.ShaderStorageBuffer)
    End Sub
End Module

Public Class RayTracing
    Inherits GameWindow
    Public Const WindowWidth As Integer = 640
    Public Const WindowHeight As Integer = 480

    Dim df As DefferedRendering
    Dim tf As TransformFeedback
    Dim trd As TriangleRegionDivision


    Public Sub New()
        MyBase.New(WindowWidth, WindowHeight, New GraphicsMode(New ColorFormat(), 24), "Nah RayTracing")
        MakeCurrent()
        Console.WriteLine("Renderer:" + GL.GetString(StringName.Renderer))
        Console.WriteLine("Vendor:" + GL.GetString(StringName.Vendor))
        Console.WriteLine("Version:" + GL.GetString(StringName.Version))
        Console.WriteLine("ShadingLanguageVersion:" + GL.GetString(StringName.ShadingLanguageVersion))
        TestACB()

        EnumerateExtensions()
        If (BindlessTextureSupported()) Then
            Console.WriteLine("BindlessTexture is Supported")
        Else
            Console.WriteLine("GPU doesn't support BindlessTexture feature")
            'Console.WriteLine("Press any key to exit")
            'Console.ReadKey(True)
            'Environment.Exit(0)
        End If

        Console.WriteLine("MaxUniformBlockSize:" + GL.GetInteger(GetPName.MaxUniformBlockSize).ToString())
        Console.WriteLine("Max2DTextureSize:" + GL.GetInteger(GetPName.MaxTextureSize).ToString())
        Console.WriteLine("Max3DTextureSize:" + GL.GetInteger(GetPName.Max3DTextureSize).ToString())
        Console.WriteLine("TextureBufferSize:" + GL.GetInteger(GetPName.MaxTextureBufferSize).ToString())
        Console.WriteLine("MaxDrawBuffers:" + GL.GetInteger(GetPName.MaxDrawBuffers).ToString())
        Console.WriteLine("MaxTextureImageUnits:" + GL.GetInteger(GetPName.MaxTextureImageUnits).ToString())
        Console.WriteLine("MaxSamples:" + GL.GetInteger(GetPName.MaxSamples).ToString())

        VSync = VSyncMode.Off
        GL.Viewport(0, 0, WindowWidth, WindowHeight)
        ShaderManager.Add("Feedback", ShaderManager.FromVS(
        IO.File.ReadAllText("shader/feedback.glvs", Text.Encoding.UTF8)))

        ShaderManager.Add("GBuffer", ShaderManager.FromVSGSFS(
        IO.File.ReadAllText("shader/gbuffer.glvs"),
        IO.File.ReadAllText("shader/gbuffer.glgs"),
        IO.File.ReadAllText("shader/gbuffer.glfs")))

        ShaderManager.Add("ShadowMap", ShaderManager.FromVSFS(
        IO.File.ReadAllText("shader/shadowmap.glvs"),
        IO.File.ReadAllText("shader/shadowmap.glfs")))

        ShaderManager.Add("RayTracing", ShaderManager.FromVSFS(
        IO.File.ReadAllText("shader/rt.glvs", Text.Encoding.UTF8),
        IO.File.ReadAllText("shader/rt.glfs", Text.Encoding.UTF8)))

        ShaderManager.Add("Basic", ShaderManager.FromVSFS(
        IO.File.ReadAllText("shader/basic.glvs", Text.Encoding.UTF8),
        IO.File.ReadAllText("shader/basic.glfs", Text.Encoding.UTF8)))

        sceneManager = LoadScene("models/scene5.dae")

        If BindlessTextureSupported() Then
            sceneManager.MakeSceneTextureHandle()
        Else
            sceneManager.MakeSceneTexture()
        End If
        sceneManager.BuildTrianglesDatas()

        Dim position = New Single(sceneManager.TriangleData.GetVertices().Length \ 3 - 1) {}
        Array.Copy(sceneManager.TriangleData.GetVertices(), position, position.Length)
        'obb = SceneManager.GetBoxObb(position)

        df = New DefferedRendering(WindowWidth, WindowHeight, sceneManager.GetTriangleCount())
        CreateFrameBufferObject(Width, Height)

        sceneManager.SetupMaterial()
        sceneManager.UpdateUniform(ShaderManager.CurrentProgram)
        tf = New TransformFeedback(sceneManager.TriangleData.GetVertices())
        trd = New TriangleRegionDivision(sceneManager.TriangleData.GetVertices())

        trd.InitImage(sceneManager.GetTriangleCount())
        trd.ComputeTriangleReflectionRegion(sceneManager.dataTexture)
        trd.ComputeTriangleShadowCasting(sceneManager.dataTexture)
    End Sub

    Dim obb As Boundingbox

    Private Sub Test()
        Dim pos = New Vector4(0, 0, -1, 1)
        Dim projection = Matrix4.CreatePerspectiveFieldOfView(45 * Math.PI / 180, 1, 1, 1000)
        Dim view = Matrix4.LookAt(New Vector3(0, 0, 0), New Vector3(0, 0, -1), New Vector3(0, 1, 0))
        Dim model = Matrix4.Identity
        Dim pos1 = Vector4.Transform(pos, model)
        Dim r = Vector4.Transform(pos, model * projection)
        Dim glPosition = r / r.W
        REM FragCoord.xyzw = glPosition(x*width,y*height,z,1/r.w)

        Dim iproj = projection.Inverted()
        Dim imv = view.Inverted()
        Dim newPos = Vector4.Transform(Vector4.Transform(New Vector4(0, 0, 0, 9), iproj), imv)
        'ndc 设备无关坐标系统
    End Sub

    Private ac As New AssimpContext()
    Public Shared sceneManager As SceneManager

    Public Function LoadScene(file As String) As SceneManager
        Dim scene As Assimp.Scene
        scene = ac.ImportFile(file, PostProcessSteps.Triangulate Or PostProcessSteps.GenerateNormals)
        Dim r = New SceneManager(scene)
        Return r
    End Function


    Private steps As UShort = 1 REM 跟蹤步數
    Public Property RTSteps As UShort
        Get
            Return steps
        End Get
        Set(value As UShort)
            If value >= 0 And value < 8 Then
                steps = value
            End If

            Dim program As Integer = ShaderManager.Active("RayTracing")
            Dim uTraceStep As Integer = GL.GetUniformLocation(program, "TraceStep")
            GL.Uniform1(uTraceStep, steps)
        End Set
    End Property

    Dim bUpdateTriangle As Boolean = False
    Dim bUpdateLight As Boolean = False

    Sub OnTrianglesChange()
        bUpdateTriangle = True
    End Sub

    Sub OnLightChange()
        bUpdateLight = True
    End Sub

    Protected Overrides Sub OnMouseMove(e As MouseMoveEventArgs)
        MyBase.OnMouseMove(e)
        If e.Mouse.RightButton = ButtonState.Pressed Then
            sceneModel = sceneModel * Matrix4.CreateRotationY(e.XDelta * 0.01)
            sceneModel = sceneModel * Matrix4.CreateRotationX(e.YDelta * 0.01)
            'Dim program As Integer = TransformFeedbackShader.Singleton.programID
            'GL.UseProgram(program) REM 注意在设置Uniform 前要先UseProgram
            'Dim uSceneModel As Integer = GL.GetUniformLocation(program, "Model")
            'GL.UniformMatrix4(uSceneModel, False, sceneModel)
            OnLightChange()
        End If
    End Sub

    Public sceneModel As Matrix4 = Matrix4.Identity
    Protected Overrides Sub OnKeyDown(e As KeyboardKeyEventArgs)
        MyBase.OnKeyDown(e)
        If e.Keyboard.Item(Key.Enter) Then
            sceneModel = sceneModel * Matrix4.CreateRotationY(0.1)

            'Dim program As Integer = TransformFeedbackShader.Singleton.programID
            'GL.UseProgram(program) REM 注意在设置Uniform 前要先UseProgram
            'Dim uSceneModel As Integer = GL.GetUniformLocation(program, "Model")
            'GL.UniformMatrix4(uSceneModel, False, sceneModel)
            OnLightChange()
        End If
    End Sub

    Private time0 As Double = Now.TimeOfDay.TotalSeconds
    Private frames As Integer
    Protected Overrides Sub OnRenderFrame(e As FrameEventArgs)
        MyBase.OnRenderFrame(e)
        Dim time1 As Double = Now.TimeOfDay.TotalSeconds
        Dim delta As Double = time1 - time0
        If delta >= 1.0 Then
            'Console.Clear()
            Console.WriteLine("FPS:{0}", (frames / delta).ToString("F2"))
            time0 = time1
            frames = 0
        End If

        GL.Clear(ClearBufferMask.ColorBufferBit Or ClearBufferMask.DepthBufferBit)
        GL.ClearColor(0.25, 0.25, 0.25, 0.0)
        GL.ClearDepth(1.0)
        GL.Enable(EnableCap.DepthTest)
        'sceneManager.UpdateUniform(ShaderManager.CurrentProgram)

        '变换反馈
        tf.Enable()
        tf.Draw(sceneModel)
        tf.Disable()

        'Dim t0 = DateTime.Now
        tf.CopyFeedbackDataToOutput(sceneManager.bufferOutput)
        Dim tfdata = sceneManager.ReadBuffer(tf.tfboPosition)
        'Dim k = SceneManager.GetBoxSphere(tfdata)

        If bUpdateTriangle Then
            trd.ComputeTriangleReflectionRegion(sceneManager.dataTexture) '模型改变应该更新
            trd.ComputeTriangleShadowCasting(sceneManager.dataTexture) '灯光或者模型改变应该更新
        End If
        If bUpdateLight Then
            trd.ComputeTriangleShadowCasting(sceneManager.dataTexture) '灯光或者模型改变应该更新
        End If

        'Dim t1 = DateTime.Now
        'Console.WriteLine("Copy time:" + (t1 - t0).TotalMilliseconds.ToString())

        '延迟渲染
        df.BeginDraw()
        df.SetupUniform(sceneModel, sceneManager.mtxView, sceneManager.mtxProjection)
        df.DrawTransformFeedBackTriangle(sceneManager.bufferOutput)
        df.EndDraw()

        '光线跟踪
        ShaderManager.Active("RayTracing")
        RTSteps = 1
        FBOBeginDraw()
        HUDSurface.Singleton.SetupTexture(sceneManager.bufferOutput, df.texPosition, df.texNormal, df.texUVID, trd.img)
        HUDSurface.Singleton.Render()
        FBOEndDraw()

        CopyToDeviceFrameBuffer(Width, Height, FrameBuffer.fbo)
        'DrawObb()

        SwapBuffers()
        frames += 1
    End Sub

    Private Sub DrawObb()
        Static vboobb = 0, iboobb = 0
        Static obbverts As Single() =
        {
        obb.Max.X, obb.Max.Y, obb.Max.Z,
        obb.Max.X, obb.Max.Y, obb.Min.Z,
        obb.Max.X, obb.Min.Y, obb.Max.Z,
        obb.Max.X, obb.Min.Y, obb.Min.Z,
        obb.Min.X, obb.Max.Y, obb.Max.Z,
        obb.Min.X, obb.Max.Y, obb.Min.Z,
        obb.Min.X, obb.Min.Y, obb.Max.Z,
        obb.Min.X, obb.Min.Y, obb.Min.Z,
        obb.Center.X, obb.Center.Y, obb.Center.Z,
        obb.vPrime.X, obb.vPrime.Y, obb.vPrime.Z,
        obb.vSecond.X, obb.vSecond.Y, obb.vSecond.Z,
        obb.vThird.X, obb.vThird.Y, obb.vThird.Z
        }

        Static ibodata As UShort() = {
        0, 1, 2, 3, 0, 2, 1, 3,
        4, 5, 6, 7, 4, 6, 5, 7,
        0, 4, 1, 5, 2, 6, 3, 7,
        8, 9, 8, 10, 8, 11
        }
        If vboobb = 0 Then
            vboobb = GL.GenBuffer()
            iboobb = GL.GenBuffer()
            GL.BindBuffer(BufferTarget.ArrayBuffer, vboobb)
            GL.BufferData(BufferTarget.ArrayBuffer, 4 * obbverts.Length, obbverts, BufferUsageHint.StaticDraw)
            GL.BindBuffer(BufferTarget.ElementArrayBuffer, iboobb)
            GL.BufferData(BufferTarget.ElementArrayBuffer, 2 * ibodata.Length, ibodata, BufferUsageHint.StaticDraw)
        End If

        Dim program = ShaderManager.Active("Basic")
        Dim uMVP = GL.GetUniformLocation(program, "MVP")
        Dim uModel = GL.GetUniformLocation(program, "Model")
        Dim mRot = Matrix4.CreateFromQuaternion(obb.Rotation) * sceneModel
        GL.UniformMatrix4(uMVP, False, mRot * sceneManager.mtxView * sceneManager.mtxProjection)
        GL.UniformMatrix4(uModel, False, mRot)

        GL.PolygonMode(MaterialFace.FrontAndBack, PolygonMode.Line)
        GL.BindBuffer(BufferTarget.ArrayBuffer, vboobb)
        GL.BindBuffer(BufferTarget.ElementArrayBuffer, iboobb)
        GL.EnableVertexAttribArray(0)
        GL.VertexAttribPointer(0, 3, VertexAttribPointerType.Float, False, 0, 0)
        GL.DrawElements(OpenGL4.PrimitiveType.Lines, ibodata.Length, DrawElementsType.UnsignedShort, IntPtr.Zero)
        GL.PolygonMode(MaterialFace.FrontAndBack, PolygonMode.Fill)
    End Sub

    Dim tfposition As IntPtr = IntPtr.Zero
    Dim tfnormal As IntPtr = IntPtr.Zero
    Dim tftexcoord As IntPtr = IntPtr.Zero

    Public Shared Sub TestOBB()
        'generate data
        Dim rand As New Random()
        Dim a, b, c, d As Single
        a = 1
        b = 4
        c = 3
        d = 2
        Dim err As Single = 10
        Dim l As New List(Of Vector4)
        If True = False Then
            Dim alllines = System.IO.File.ReadAllLines("D:\\points.txt")
            For Each line In alllines
                Dim xyz = line.Split(",")
                l.Add(New Vector4(Single.Parse(xyz(0)), Single.Parse(xyz(1)), Single.Parse(xyz(2)), 0))
            Next
        Else
            Dim fs = System.IO.File.Open("D:\\points.txt", FileMode.Create, FileAccess.Write)
            Dim w As New System.IO.StreamWriter(fs)
            For i = 0 To 200 - 1
                Dim x = i, y = i * 3
                Dim z = (-a * x - b * y - d) / c
                y += (rand.NextDouble() - 0.5) * err
                Dim v As New Vector4(x, y, z, 0)
                l.Add(v)
                w.WriteLine(String.Format("{0},{1},{2}", v.X, v.Y, v.Z))
            Next
            w.Flush()
            w.Close()
            fs.Close()
        End If
        Dim arr = l.ToArray()
        Dim arrptr As IntPtr = Marshal.UnsafeAddrOfPinnedArrayElement(arr, 0)
        Dim arr2 = New Single(arr.Length * 4 - 1) {}
        Marshal.Copy(arrptr, arr2, 0, arr.Length * 4)
        Dim obb = SceneManager.GetBoxObb(arr2)
    End Sub

    Public Shared Sub Main()
        'Dim r = Mathematical.CubertNewton(0.7)
        'Console.WriteLine("root ={0}", r)
        'Dim r0 = Helper.Solve_x1p3(1, 15, 18, 30)
        'Dim m As New Matrix3(1, 3, 5, 3, 9, 15, 9, 27, 45)
        'Dim m As Matrix3 = Matrix3.CreateScale(3)
        'Dim r1 = Helper.EigenValue(m)
        'Helper.EigenVector(m, r1)
        'Do
        '    TestOBB()
        '    Threading.Thread.Sleep(1)
        'Loop Until True = False
        'Console.ReadKey(True)
        Using rt As New RayTracing()
            rt.Run()
        End Using
    End Sub

End Class

